package com.log4jviewer.domain;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import com.log4jviewer.LogEventListener;
import com.log4jviewer.filters.LogFilterEngine;

/**
 * Class represents model for logs.
 * 
 * @author <a href="mailto:rd.ryly@gmail.com">Ruslan Diachenko</a>
 */
public final class LogList implements LogEventListener {

    private final List<LogEvent> allLogs;

    private final List<LogEvent> filteredLogs;

    private final LogFilterEngine logFilterEngine;

    private Set<LogListListener> listeners;

    private int logBufferSize;

    public LogList(final LogFilterEngine logFilterEngine, final int logBufferSize) {
        this.logFilterEngine = logFilterEngine;
        this.logBufferSize = logBufferSize;
        allLogs = new LinkedList<LogEvent>();
        filteredLogs = new LinkedList<LogEvent>();
        listeners = new HashSet<LogListListener>();
    }

    public List<LogEvent> getFilteredLogs() {
        return filteredLogs;
    }

    public int getFilteredLogsNum() {
        return filteredLogs.size();
    }

    public LogEvent getFilteredLog(final int index) {
        return filteredLogs.get(index);
    }

    public void updateLogBufferSize(final int logBufferSize) {
        this.logBufferSize = logBufferSize;
    }

    @Override
    public void addLog(final LogEvent log) {
        allLogs.add(log);
        matchFilter(log);

        // If there's a buffer overflow then update log list removing
        // first logs and adding the new ones to the end of this list.
        doLogBufferValidation();
    }

    private void doLogBufferValidation() {
        int bufferOverflowSize = 0;

        if (allLogs.size() > logBufferSize) {
            int removedLogsNumber = allLogs.size() - logBufferSize;

            for (int i = 0; i < removedLogsNumber; i++) {
                LogEvent removedLog = allLogs.get(0);
                int logIndex = filteredLogs.indexOf(removedLog);

                if (logIndex > -1) {
                    filteredLogs.remove(logIndex);
                    bufferOverflowSize++;
                }
                allLogs.remove(0);
            }

            if (bufferOverflowSize > 0) {
                // Notify observers about structure changes in Log table.
                notifyListeners(this, bufferOverflowSize);
            }
        }
    }

    private void matchFilter(final LogEvent log) {
        if (logFilterEngine.isFiltered(log)) {
            filteredLogs.add(log);

            // Notify observers about adding a new log which passed filter.
            notifyListeners(log);
        }
    }

    public void notifyListeners(final LogEvent newLog) {
        for (LogListListener listener : listeners) {
            listener.addLog(newLog);
        }
    }

    public void notifyListeners(final LogList event, final int bufferOverflowSize) {
        for (LogListListener listener : listeners) {
            listener.handleLogBufferOverflowEvent(event, bufferOverflowSize);
        }
    }

    public void addListener(final LogListListener listener) {
        if (listener != null) {
            listeners.add(listener);
        }
    }

    public void removeListener(final LogListListener listener) {
        listeners.remove(listener);
    }

    public void revalidate() {
        filteredLogs.clear();

        for (LogEvent log : allLogs) {
            matchFilter(log);
        }
    }

    public void clear() {
        allLogs.clear();
        filteredLogs.clear();
    }
}
